package heap

import (
	"jvmgo/classfile"
	"strings"
)

type Class struct {
	jClass            *Object // java.lang.Class 实例
	accessFlags       uint16
	name              string
	superClassName    string
	interfaceNames    []string
	constantPool      *ConstantPool
	fields            []*Field
	methods           []*Method
	loader            *ClassLoader
	superClass        *Class
	interfaces        []*Class
	instanceSlotCount uint
	staticSlotCount   uint
	staticVars        Slots
	initStarted       bool
	sourceFile        string
}

func (self *Class) InitStarted() bool {
	return self.initStarted
}

func (self *Class) StartInit() {
	self.initStarted = true
}

func newClass(cf *classfile.ClassFile) *Class {
	class := &Class{}
	class.accessFlags = cf.AccessFlags()
	class.name = cf.ClassName()
	class.superClassName = cf.SuperClassName()
	class.interfaceNames = cf.InterfaceNames()
	class.constantPool = newConstantPool(class, cf.ConstantPool())
	class.fields = newFields(class, cf.Fields())
	class.methods = newMethods(class, cf.Methods())
	class.sourceFile = getSourceFile(cf)
	return class
}

func getSourceFile(cf *classfile.ClassFile) string {
	if sfAttr := cf.SourceFileAttribute(); sfAttr != nil {
		return sfAttr.FileName()
	}
	return "Unknown"
}

func (self *Class) IsPublic() bool {
	return 0 != self.accessFlags&ACC_PUBLIC
}

func (self *Class) IsAbstract() bool {
	return 0 != (self.accessFlags & ACC_ABSTRACT)
}

func (self *Class) isAccessibleTo(other *Class) bool {
	return self.IsPublic() || self.GetPackageName() == other.GetPackageName()
}

func (self *Class) GetPackageName() string {
	if i := strings.LastIndex(self.name, "/"); i >= 0 {
		return self.name[:i]
	}
	return ""
}

func (self *Class) NewObject() *Object {
	return newObject(self)
}

func newObject(class *Class) *Object {
	return &Object{
		class: class,
		data:  newSlots(class.instanceSlotCount),
	}
}

func (self *Class) isSubClassOf(other *Class) bool {
	for c := self.superClass; c != nil; c = c.superClass {
		if c == other {
			return true
		}
	}
	return false
}

func (self *Class) isImplements(iface *Class) bool {
	for c := self; c != nil; c = c.superClass {
		for _, i := range c.interfaces {
			if i == iface || i.isSubInterfaceOf(iface) {
				return true
			}
		}
	}
	return false
}

func (self *Class) isSubInterfaceOf(iface *Class) bool {
	for _, superInterface := range self.interfaces {
		if superInterface == iface || superInterface.isSubInterfaceOf(iface) {
			return true
		}
	}
	return false
}

func (self *Class) GetMainMethod() *Method {
	return self.getStaticMethod("main", "([Ljava/lang/String;)V")
}

//(Ljava/lang/String;)V
//(Ljava/lang/String;)V

func (self *Class) getStaticMethod(name, descriptor string) *Method {
	for _, method := range self.methods {
		if method.IsStatic() && method.name == name && method.descriptor == descriptor {
			return method
		}
	}
	return nil
}

func (self *Class) ConstantPool() *ConstantPool {
	return self.constantPool
}

func (self *Class) IsAssignableFrom(other *Class) bool {
	s, t := other, self
	if s == t {
		return true
	}
	if !s.IsArray() {
		if !s.IsInterface() {
			if !t.IsInterface() {
				return s.isSubClassOf(t)
			} else {
				return s.isImplements(t)
			}
		} else {
			if !t.IsInterface() {
				//return other == self.loader.LoadClass("java/lang/Object")
				return t.isJ1Object()
			} else {
				return t.isSuperInterfaceOf(s)
			}
		}
	} else {
		if !t.IsArray() {
			if !t.IsInterface() {
				return t.isJ1Object()

			} else {
				return t.isJ1Cloneable() || t.isJioSerializable()
			}
		} else {
			sc := s.ComponentClass()
			tc := t.ComponentClass()
			return sc == tc || tc.IsAssignableFrom(sc)
		}
	}
}

func (self *Class) isJ1Object() bool {
	return self == self.loader.LoadClass("java/lang/Object")
}
func (self *Class) isJ1Cloneable() bool {
	return self == self.loader.LoadClass("java/lang/Cloneable")
}
func (self *Class) isJioSerializable() bool {
	return self == self.loader.LoadClass("java/io/Serializable")
}

func (self *Class) IsArray() bool {
	return self.name[0] == '['
}

func (self *Class) IsInterface() bool {
	v := (self.accessFlags & ACC_INTERFACE)
	return v != 0
}

func (self *Class) ComponentClass() *Class {
	componentClassName := getComponentClassName(self.name)
	return self.loader.LoadClass(componentClassName)
}

func (self *Class) IsPrimitive() bool {
	_, ok := PrimitiveTypes[self.name]
	return ok
}

func getComponentClassName(name string) string {
	if name[0] == '[' {
		componentTypeDesc := name[1:]
		return toClassName(componentTypeDesc)
	}
	panic("no array" + name)
}

func toClassName(desc string) string {
	if desc[0] == '[' {
		return desc
	}
	if desc[0] == 'L' {
		return desc[1 : len(desc)-1]
	}
	for className, d := range PrimitiveTypes {
		if d == desc {
			return className
		}
	}
	panic("invalid desc: " + desc)
}

func (self *Class) StaticVars() Slots {
	return self.staticVars
}

func (self *Class) IsImplements(other *Class) bool {
	for c := self; c != nil; c = c.superClass {
		interfaces := c.interfaces
		for _, interfaceClass := range interfaces {
			if interfaceClass == other || interfaceClass.isSubInterfaceOf(other) {
				return true
			}
		}
	}
	return false
}

func (self *Class) IsSuperClassOf(clazz *Class) bool {
	return (clazz.superClass == self)
}

func (self *Class) IsSubClassOf(clazz *Class) bool {
	return (self.superClass == clazz)
}

func (self *Class) SuperClass() *Class {
	return self.superClass
}

func (self *Class) IsSuper() bool {
	return self.accessFlags&ACC_SUPER != 0
}

func (self *Class) Name() string {
	return self.name
}

func (self *Class) GetClinitMethod() *Method {
	return self.getStaticMethod("<clinit>", "()V")
}

func (self *Class) ArrayClass() *Class {
	arrayClassName := getArrayClassName(self.name)
	return self.loader.LoadClass(arrayClassName)
}

func getArrayClassName(className string) string {
	return "[" + toDescriptor(className)
}

func toDescriptor(className string) string {
	if className[0] == '[' {
		return className
	}
	if d, ok := PrimitiveTypes[className]; ok {
		return d
	}
	return "L" + className + ";"
}

func (self *Class) getField(name, descriptor string, isStatic bool) *Field {
	for c := self; c != nil; c = c.superClass {
		for _, field := range c.fields {
			if field.IsStatic() == isStatic && field.name == name && field.descriptor == descriptor {
				return field
			}
		}
	}
	return nil
}

func (self *Class) Loader() *ClassLoader {
	return self.loader
}

//func (self *Class) JClass() *Object {
//	return self.jClass
//}

func (self *Class) isSuperInterfaceOf(iface *Class) bool {
	return iface.isSubInterfaceOf(self)
}

func (self *Class) JClass() *Object {
	return self.jClass
}

func (self *Class) JavaName() string {
	return strings.ReplaceAll(self.name, "/", ".")
}

func (self *Class) GetRefVar(fieldName string, fieldDescriptor string) *Object {
	field := self.getField(fieldName, fieldDescriptor, true)
	return self.staticVars.GetRef(field.slotId)

}

func (self *Class) GetInstanceMethod(methodName string, descriptor string) *Method {
	return self.getMethod(methodName, descriptor, false)
}

func (self *Class) getMethod(name string, descriptor string, isStatic bool) *Method {
	for c := self; c != nil; c = c.superClass {
		methods := c.methods
		for _, method := range methods {
			if method.IsStatic() == isStatic && method.name == name && method.descriptor == descriptor {
				return method
			}
		}
	}
	return nil
}

func (self *Class) SourceFile() string {
	return self.sourceFile
}

func (self *Class) SetRefVar(fieldName string, fieldDescriptor string, ref *Object) {
	field := self.getField(fieldName, fieldDescriptor, true)
	self.staticVars.SetRef(field.slotId, ref)
}

func (self *Class) GetStaticMethod(methodName string, methodDescriptor string) *Method {
	methods := self.methods
	for _, method := range methods {
		if method.IsStatic() && method.Name() == methodName && method.descriptor == methodDescriptor {
			return method
		}
	}
	return nil
}

func (self *Class) AccessFlags() uint16 {
	return self.accessFlags
}

func (self *Class) GetFields(publicOnly bool) []*Field {
	if publicOnly {
		publicFields := make([]*Field, 0, len(self.fields))
		for _, field := range self.fields {
			if field.IsPublic() {
				publicFields = append(publicFields, field)
			}
		}
		return publicFields
	} else {
		return self.fields
	}
}

func (self *Class) GetConstructor(descriptor string) *Method {
	return self.GetInstanceMethod("<init>", descriptor)
}

func (self *Class) GetConstructors(publicOnly bool) []*Method {
	constructors := make([]*Method, 0, len(self.methods))
	for _, method := range self.methods {
		if method.isConstructor() {
			if !publicOnly || method.IsPublic() {
				constructors = append(constructors, method)
			}
		}
	}
	return constructors
}

func (self *Class) Interfaces() []*Class {
	return self.interfaces
}

func (self *Class) GetMethods(publicOnly bool) []*Method {
	methods := make([]*Method, 0, len(self.methods))
	for _, method := range self.methods {
		if !method.isClinit() && !method.IsPublic() {
			if !publicOnly || method.IsPublic() {
				methods = append(methods, method)
			}
		}
	}
	return methods
}
